#ifndef INSTRUCTION_H_INCLUDED
#define INSTRUCTION_H_INCLUDED

#include <stdint.h>
#include <string>
#define COND_SHIFT 28
#define CLASS_SHIFT 26
#define FUNC_SHIFT 16
#define IMMB_SHIFT 15
#define REG_A_SHIFT 10
#define REG_B_SHIFT 5
#define REG_DEST_SHIFT 0
#define ALU_WR_BACK_BIT_SHIFT 21

#define ALU_OP_MASK 0x000F8000
#define REG_A_MASK 0x00007C00
#define REG_B_MASK 0x000003E0
#define REG_DEST_MASK 0x0000001F
#define CLASS_MASK 0x0C000000
#define COND_MASK 0xF0000000

struct Instruction
{
    enum Cond
    {
        COND_ALWAYS = 0xF,
        COND_EQUAL = 0b1001,
        COND_NEQUAL = 0b0001,
        COND_LESS = 0b1010,
        COND_GR_EQ = 0b0010,
        COND_GREATER = 0b1011,
        COND_L_EQ = 0b0011,
        COND_CARRY = 0b1100,
        COND_NCARRY = 0b0100
    };

    enum Class
    {
        CLASS_ALU = 0b01,
        CLASS_LDST = 0b10,
        CLASS_JUMP = 0b11
    };

    enum Function
    {
        F_JUMP_REL	= 1,
        F_JUMP_ABS	= 2,
        F_LDST_LOAD = 0,
        F_LDST_STORE = 1
    };

    enum AluOp
    {
        ALU_PASS_B  = 1,
        ALU_NEG = 2,
        ALU_ADD = 3,
        ALU_SUB = 4,
        ALU_AND = 5,
        ALU_OR = 6,
        ALU_NAND = 7,
        ALU_XOR = 8,
        ALU_NOT = 9,
        ALU_SHIFT_L = 10,
        ALU_SHIFT_R = 11,
        ALU_SHIFT_A = 12,
        ALU_SWAP16 = 13,
        ALU_SWAP8  = 14,
        ALU_BSWAP  = 15
    };

    //! TODO initialize to nop?
    Instruction(uint32_t w = 0)
    {
       word = w;
    }

    void clear()
    {
        word = 0;
        labelB.clear();
    }

    void setCond(Cond cond)
    {
       word &= ~COND_MASK; // remove condition
       word |= ((uint32_t)cond << COND_SHIFT) & COND_MASK;
    }

    Instruction::Cond getCond()const
    {
        return (Cond)((word & COND_MASK) >> COND_SHIFT);
    }

    void setClass(Class clazz)
    {
       word &= ~CLASS_MASK;
       word |= ((uint32_t)clazz << CLASS_SHIFT) & CLASS_MASK;
    }

    Class getClass() const
    {
        return (Class)((word & CLASS_MASK) >> CLASS_SHIFT);
    }

    void setAluWriteBackBit(uint8_t bit)
    {
       word &= ~(0b1U << ALU_WR_BACK_BIT_SHIFT);
       word |= ((uint32_t)bit & 1) << ALU_WR_BACK_BIT_SHIFT;
    }

    void setAluOp(AluOp aluop)
    {
       word &= ~ALU_OP_MASK;
       word |= ((uint32_t)aluop << FUNC_SHIFT) & ALU_OP_MASK;
    }

    AluOp getAluOp() const
    {
        return (AluOp)((word & ALU_OP_MASK) >> FUNC_SHIFT);
    }

    bool isWriteBackBitSet() const
    {
        return word & (0b100000 << FUNC_SHIFT);
    }

    void setFunction(Function func)
    {
       word &= ~(0b111111 << FUNC_SHIFT);
       word |= ((uint32_t)func & 0b111111)  << FUNC_SHIFT;
    }

    Function getFunction() const
    {
        return (Function)((word >> FUNC_SHIFT) & 0b111111);
    }

    void setOpA(uint8_t a)
    {
       word &= ~REG_A_MASK;
       word |= ((uint32_t)a << REG_A_SHIFT) & REG_A_MASK;
    }

    uint32_t getOpA() const
    {
       return (word & REG_A_MASK) >> REG_A_SHIFT;
    }

    void setOpB(uint8_t b)
    {
       word &= ~REG_B_MASK;
       word |= ((uint32_t)b << REG_B_SHIFT) & REG_B_MASK;
    }

    uint32_t getOpB() const
    {
       return (word & REG_B_MASK) >> REG_B_SHIFT;
    }

    void setImmB(uint8_t i)
    {
       word &= ~(1U << IMMB_SHIFT);
       word |= ((uint32_t)i & 1) << IMMB_SHIFT;
    }

    bool isImmB() const
    {
        return (word >> IMMB_SHIFT) & 1;
    }

    void setDest(uint8_t dest)
    {
       word &= ~REG_DEST_MASK;
       word |= ((uint32_t)dest << REG_DEST_SHIFT) & REG_DEST_MASK;
    }

    uint32_t getDest() const
    {
       return (word & REG_DEST_MASK) >> REG_DEST_SHIFT;
    }

    uint32_t word;
    std::string labelB;
};

std::ostream& operator<<(std::ostream& out, const Instruction& inst);

#endif // INSTRUCTION_H_INCLUDED
